package rnxextract

import (
	"fmt"
	"gitee.com/ymofen/gobase"
	"gitee.com/ymofen/golog"
	"gnssa/pkg/rnxutils"
	"gnssa/pkg/utils"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"time"
)

type fileInfo struct {
	fullName  string
	staid     string
	LongName  string
	rnxHeader rnxutils.RinexHeader

	xyz       [3]float64
	timelist  []string
	blockList [3]string
}

type RnxFilesExtract struct {
	cachefile string
}

func NewRnxFilesExtract() *RnxFilesExtract {
	return &RnxFilesExtract{}
}

func (this *RnxFilesExtract) readRnxFile(info *fileInfo, fileName string) (err error) {
	{
		str := gobase.ExtractFileName(fileName)
		strs := strings.Split(str, "_")
		if len(strs) >= 6 {
			info.LongName = strs[0]
		}
	}

	var rnxHeader rnxutils.RinexHeader
	err = rnxHeader.ReadFromFile(fileName)
	if err != nil {
		return err
	}
	if rnxHeader.EndHeaderFlag != 1 {
		return fmt.Errorf("invalid rinex header")
	}
	if len(rnxHeader.MarkerName) > 4 {
		rnxHeader.MarkerName = rnxHeader.MarkerName[:4]
	}
	rnxHeader.LastTime = gobase.TryStrToTime("2099-12-31 00:00:00", time.UTC, gobase.ZeroTime)
	info.rnxHeader = rnxHeader
	info.staid = info.rnxHeader.MarkerName

	{
		// STATION NAME          FLG          FROM                   TO         OLD STATION NAME      REMARK
		//****************      ***  YYYY MM DD HH MM SS  YYYY MM DD HH MM SS  ********************  ************************
		//BRST 10004M004        001                                            BRST*                 STN
		var sb gobase.BytesBuilder
		sb.AppendFixedStr(fmt.Sprintf("%s %s", rnxHeader.MarkerName, rnxHeader.MarkerNO), 22, " ")
		sb.AppendFixedStr("001", 5, " ")
		sb.AppendFixedStr(rnxutils.ParseRinexTime(rnxHeader.FirstTime, "yyyy mm dd hh mi ss"), 21, " ")
		sb.AppendFixedStr(rnxutils.ParseRinexTime(rnxHeader.LastTime, "yyyy mm dd hh mi ss"), 21, " ")
		sb.AppendFixedStr(fmt.Sprintf("%s*", rnxHeader.MarkerName), 22, " ")
		sb.AppendStr("STN")
		info.blockList[0] = sb.String()
	}

	{
		//STATION NAME          FLG          FROM                   TO         RECEIVER TYPE         RECEIVER SERIAL NBR   REC #   ANTENNA TYPE          ANTENNA SERIAL NBR    ANT #    NORTH      EAST      UP     AZIMUTH  LONG NAME  DESCRIPTION             REMARK
		//****************      ***  YYYY MM DD HH MM SS  YYYY MM DD HH MM SS  ********************  ********************  ******  ********************  ********************  ******  ***.****  ***.****  ***.****  ******  *********  **********************  ************************
		//BRST 10004M004        001  1998 10 31 00 00 00  2006 07 25 00 00 00  TRIMBLE 4000SSI                             999999  TRM29659.00     NONE                        999999    0.0000    0.0000    1.0543          BRST00FRA  Brest, FR               brst_20240702.log
		//BRST 10004M004        001  2006 07 26 00 00 00  2007 04 19 00 00 00  TRIMBLE 5700                                999999  TRM41249.00     NONE                        999999    0.0000    0.0000    1.0567          BRST00FRA  Brest, FR               brst_20240702.log
		var sb gobase.BytesBuilder
		sb.AppendFixedStr(fmt.Sprintf("%s %s", rnxHeader.MarkerName, rnxHeader.MarkerNO), 22, " ")
		sb.AppendFixedStr("001", 5, " ")
		sb.AppendFixedStr(rnxutils.ParseRinexTime(rnxHeader.FirstTime, "yyyy mm dd hh mi ss"), 21, " ")
		sb.AppendFixedStr(rnxutils.ParseRinexTime(rnxHeader.LastTime, "yyyy mm dd hh mi ss"), 21, " ")
		sb.AppendFixedStr(rnxHeader.RecType, 22, " ")
		rnxutils.WriteFixedWidthStr(&sb, rnxHeader.RecNO, 22)
		recNo := rnxHeader.RecNO
		if len(recNo) > 5 {
			recNo = recNo[len(recNo)-5:]
		}
		rnxutils.WriteFixedWidthStr(&sb, recNo, 8)
		rnxutils.WriteFixedWidthStr(&sb, rnxHeader.AntennaType, 22)

		{
			sb.AppendFixedStr(rnxHeader.AntennaNO, 22, " ")
			str0 := rnxHeader.AntennaNO
			if len(str0) > 0 {
				str0 = rnxutils.DelNonNumberChar(str0)
				if len(str0) > 5 {
					str0 = str0[len(str0)-5:]
				}
			}
			rnxutils.WriteRinexFloat(&sb, 6, gobase.StrToFloat64Def(str0, 0), 0)
			sb.AppendStr("  ")
		}

		// H/E/N
		rnxutils.WriteRinexFloat(&sb, 8, rnxHeader.AntennaDelta[2], 4)
		sb.AppendStr("  ")
		rnxutils.WriteRinexFloat(&sb, 8, rnxHeader.AntennaDelta[1], 4)
		sb.AppendStr("  ")
		rnxutils.WriteRinexFloat(&sb, 8, rnxHeader.AntennaDelta[0], 4)
		sb.AppendStr("  ")

		// AZIMUTH
		// WriteFixedWidthStr(&sb, "0.0", 8)
		rnxutils.WriteRinexFloat(&sb, 8, 0, 1)

		//WriteFixedWidthStr(&sb, info.LongName, 11)
		rnxutils.WriteFixedWidthStr(&sb, "", 11)
		rnxutils.WriteFixedWidthStr(&sb, "", 24)
		rnxutils.WriteFixedWidthStr(&sb, "", 24)

		info.blockList[1] = sb.String()
	}

	return nil
}

func (this *RnxFilesExtract) readRnxFileV0(info *fileInfo, fileName string) (err error) {
	//   4231162.7790  -332746.9230  4745130.6810                  APPROX POSITION XYZ
	var block2 [3]string

	// STATION NAME          FLG          FROM                   TO         OLD STATION NAME      REMARK
	//****************      ***  YYYY MM DD HH MM SS  YYYY MM DD HH MM SS  ********************  ************************
	//BRST 10004M004        001                                            BRST*                 STN
	//GANP 11515M001        001                                            GANP*                 NEW (EUREF)
	//HERT 13212M010        001                                            HERT*                 STN (IGLOS)
	var block3 [5]string

	_, err = gobase.ReadFileLine(fileName, func(idx int64, line []byte) bool {
		str := gobase.Trim(string(line))
		if strings.HasSuffix(str, "MARKER NAME") {
			str0 := gobase.ReplaceMultiSpace2One(str, " ")
			s1, _ := gobase.Split2Str(str0, " ")
			block2[0] = s1
		} else if strings.HasSuffix(str, "MARKER NUMBER") {
			str0 := gobase.ReplaceMultiSpace2One(str, " ")
			s1, _ := gobase.Split2Str(str0, " ")
			block2[1] = s1
		} else if strs0, ok0 := rnxutils.SplitWithWidth(str, "TIME OF FIRST OBS"); ok0 {
			//   2024     7    20     0     0    0.0000000     GPS         TIME OF FIRST OBS
			for i := 0; i < len(strs0); i++ {
				f0 := gobase.StrToFloat64Def(strs0[i], -1)
				if f0 < 60 {
					strs0[i] = fmt.Sprintf("%.2d", int(f0))
				}
			}
			strs0 = strs0[:len(strs0)-1]

			block2[2] = strings.Join(strs0, " ")

			block3[2] = strings.Join(strs0, " ")
			info.rnxHeader.FirstTime = time.Date(gobase.StrToIntDef(strs0[0], 0), time.Month(gobase.StrToIntDef(strs0[1], 0)), gobase.StrToIntDef(strs0[2], 0),
				gobase.StrToIntDef(strs0[3], 0), gobase.StrToIntDef(strs0[4], 0), gobase.StrToIntDef(strs0[4], 0), 0, time.UTC)
		} else if strs0, ok0 := rnxutils.SplitWithWidth(str, "TIME OF LAST OBS"); ok0 {
			//   2024     7    20     0     0    0.0000000     GPS         TIME OF LAST OBS
			for i := 0; i < len(strs0); i++ {
				f0 := gobase.StrToFloat64Def(strs0[i], -1)
				if f0 < 60 {
					strs0[i] = fmt.Sprintf("%.2d", int(f0))
				}
			}
			strs0 = strs0[:len(strs0)-1]
			block3[3] = strings.Join(strs0, " ")
		} else if strs0, ok0 := rnxutils.SplitWithWidth(str, "REC # / TYPE / VERS", 20, 20, 20); ok0 {
			// 5818R40023          TRIMBLE ALLOY       5.45                REC # / TYPE / VERS
			block3[4] = strs0[1]
		} else if strings.HasSuffix(str, "APPROX POSITION XYZ") {
			xyzstr := gobase.ReplaceMultiSpace2One(str, " ")
			info.xyz[0], info.xyz[1], info.xyz[2] = gobase.StrTo3Float64(xyzstr, " ")
		}
		return true
	})
	if info.rnxHeader.FirstTime.IsZero() {
		return
	}

	block3[0] = block2[0]
	block3[1] = block2[1]
	info.rnxHeader.MarkerName = block2[0]
	{
		// STATION NAME          FLG          FROM                   TO         OLD STATION NAME      REMARK
		//****************      ***  YYYY MM DD HH MM SS  YYYY MM DD HH MM SS  ********************  ************************
		//BRST 10004M004        001                                            BRST*                 STN
		var sb gobase.BytesBuilder
		sb.AppendFixedStr(fmt.Sprintf("%s %s", block2[0], block2[1]), 22, " ")
		sb.AppendFixedStr("001", 5, " ")
		sb.AppendStr(block2[2])
		sb.AppendStr("                       ")
		sb.AppendFixedStr(fmt.Sprintf("%s*", block2[0]), 22, " ")
		sb.AppendStr("STN")
		info.blockList[0] = sb.String()
	}

	{
		//STATION NAME          FLG          FROM                   TO         RECEIVER TYPE         RECEIVER SERIAL NBR   REC #   ANTENNA TYPE          ANTENNA SERIAL NBR    ANT #    NORTH      EAST      UP     AZIMUTH  LONG NAME  DESCRIPTION             REMARK
		//****************      ***  YYYY MM DD HH MM SS  YYYY MM DD HH MM SS  ********************  ********************  ******  ********************  ********************  ******  ***.****  ***.****  ***.****  ******  *********  **********************  ************************
		//BRST 10004M004        001  1998 10 31 00 00 00  2006 07 25 00 00 00  TRIMBLE 4000SSI                             999999  TRM29659.00     NONE                        999999    0.0000    0.0000    1.0543          BRST00FRA  Brest, FR               brst_20240702.log
		//BRST 10004M004        001  2006 07 26 00 00 00  2007 04 19 00 00 00  TRIMBLE 5700                                999999  TRM41249.00     NONE                        999999    0.0000    0.0000    1.0567          BRST00FRA  Brest, FR               brst_20240702.log
		var sb gobase.BytesBuilder
		sb.AppendFixedStr(fmt.Sprintf("%s %s", block3[0], block3[1]), 22, " ")
		sb.AppendFixedStr("001", 5, " ")
		sb.AppendFixedStr(block3[2], 21, " ")
		sb.AppendFixedStr(block3[3], 21, " ")
		sb.AppendFixedStr(block3[4], 22, " ")

		info.blockList[1] = sb.String()
	}

	return err
}

// BRST00FRA_R_20230110000_01D_30S_MO.crx
func (this *RnxFilesExtract) processGzFile(info *fileInfo, fullName string) (err error) {
	var newFile string
	newFile, err = utils.DecompressZipToCurrentDir(fullName)
	if err != nil {
		golog.Warnf("DecompressZipToCurrentDir %s err:%s", fullName, err.Error())
		return err
	}

	defer gobase.DeleteFileOrDir(newFile)

	return this.readRnxFile(info, newFile)
}

// BRST00FRA_R_20230110000_01D_30S_MO.23o
func (this *RnxFilesExtract) processRnxObsFile(info *fileInfo, fullName string) (err error) {
	return this.readRnxFile(info, fullName)
}

func (this *RnxFilesExtract) writeStaFile(outputFile string, fileList []fileInfo) (err error) {
	var fileMap = make(map[string]*fileInfo)
	for i := 0; i < len(fileList); i++ {
		itm := fileMap[fileList[i].staid]
		if itm == nil {
			itm = &fileList[i]
			fileMap[fileList[i].staid] = itm
		}
		itm.timelist = append(itm.timelist, fmt.Sprintf("%.4d%.3d", fileList[i].rnxHeader.FirstTime.Year(), fileList[i].rnxHeader.FirstTime.Day()))
	}

	lst := make([]fileInfo, 0, len(fileList))
	for _, v := range fileMap {
		sort.Strings(v.timelist)
		lst = append(lst, *v)
	}
	sort.Slice(lst, func(i, j int) bool {
		return lst[i].staid < lst[j].staid
	})

	nowT := time.Now()
	var sb gobase.BytesBuilder
	sb.Appendf(`EXAMPLE STATION INFORMATION FILE                                 %s
--------------------------------------------------------------------------------

FORMAT VERSION: 1.03
TECHNIQUE:      GNSS
`, nowT.Format("02-Jan-06 15:04"))

	sb.Appendf(`TYPE 001: RENAMING OF STATIONS
------------------------------

STATION NAME          FLG          FROM                   TO         OLD STATION NAME      REMARK
****************      ***  YYYY MM DD HH MM SS  YYYY MM DD HH MM SS  ********************  ************************
`)

	for _, v := range lst {
		sb.Appendf("%s\n", v.blockList[0])
	}
	sb.Appendf("\n")

	sb.Appendf(`TYPE 002: STATION INFORMATION
-----------------------------

STATION NAME          FLG          FROM                   TO         RECEIVER TYPE         RECEIVER SERIAL NBR   REC #   ANTENNA TYPE          ANTENNA SERIAL NBR    ANT #    NORTH      EAST      UP     AZIMUTH  LONG NAME  DESCRIPTION             REMARK
****************      ***  YYYY MM DD HH MM SS  YYYY MM DD HH MM SS  ********************  ********************  ******  ********************  ********************  ******  ***.****  ***.****  ***.****  ******  *********  **********************  ************************
`)

	for _, v := range lst {
		sb.Appendf("%s\n", v.blockList[1])
	}
	sb.Appendf("\n")

	sb.Appendf(`TYPE 003: HANDLING OF STATION PROBLEMS
--------------------------------------

STATION NAME          FLG          FROM                   TO         REMARK
****************      ***  YYYY MM DD HH MM SS  YYYY MM DD HH MM SS  ************************************************************


TYPE 004: STATION COORDINATES AND VELOCITIES (ADDNEQ)
-----------------------------------------------------
                                            RELATIVE CONSTR. POSITION     RELATIVE CONSTR. VELOCITY
STATION NAME 1        STATION NAME 2        NORTH     EAST      UP        NORTH     EAST      UP
****************      ****************      **.*****  **.*****  **.*****  **.*****  **.*****  **.*****


TYPE 005: HANDLING STATION TYPES
--------------------------------

STATION NAME          FLG  FROM                 TO                   MARKER TYPE           REMARK
****************      ***  YYYY MM DD HH MM SS  YYYY MM DD HH MM SS  ********************  ************************




`)
	err = gobase.RewriteFile(outputFile, sb.Bytes())
	return err
}

func extractFromFileName(info *fileInfo, fullName string) (validFlag bool) {
	fname := gobase.ExtractFileName(fullName)
	ext := filepath.Ext(fullName)

	{
		// GANP00SVK_R_20230110000_01D_MN.rnx
		// BRST00FRA_R_20230110000_01D_30S_MO.23o
		// WTZZ00DEU_R_20242020000_01D_30S_MO.crx
		nameTokens := strings.Split(gobase.DeleteFileNameExt(fname), "_")
		if len(nameTokens) == 6 || len(nameTokens) == 5 {
			info.staid = nameTokens[0]
			syear := nameTokens[2][:4]
			sdoy := nameTokens[2][4:7]
			t0 := time.Date(gobase.StrToIntDef(syear, 0), 1, 1, 0, 0, 0, 0, time.UTC)
			t0 = t0.Add(time.Duration(gobase.StrToIntDef(sdoy, 0)-1) * time.Hour * 24)
			validFlag = true
			return
		}
	}

	// MORP2020.24D, gdzj2790.24o
	{
		if len(fname) == 12 {
			fname = gobase.DeleteFileNameExt(fname)
			staId := fname[:len(fname)-4]
			syear := "20" + ext[1:3]
			sdoy := fname[len(fname)-4 : len(fname)-1]
			t0 := time.Date(gobase.StrToIntDef(syear, 0), 1, 1, 0, 0, 0, 0, time.UTC)
			t0 = t0.Add(time.Duration(gobase.StrToIntDef(sdoy, 0)-1) * time.Hour * 24)
			validFlag = true
			info.staid = staId
			return
		}
	}

	return false
}

func (this *RnxFilesExtract) Search(dir string, outputFile string) {
	var fileList []fileInfo

	t0 := time.Now()
	gobase.RangeFiles(dir, 1, func(path string, file os.FileInfo) bool {
		fileName := file.Name()
		fileNameUpper := strings.ToUpper(fileName)
		ext := strings.ToUpper(filepath.Ext(fileName))
		if strings.HasSuffix(fileName, "_MN.rnx.gz") { // 不处理
			return true
		}

		if ext == ".GZ" || strings.HasSuffix(fileNameUpper, ".Z") {
			info := fileInfo{fullName: dir + gobase.PathSeparator + fileName}
			err := this.processGzFile(&info, info.fullName)
			if err != nil {
				golog.Errorf("%s err %s", fileName, err.Error())
			} else {
				extractFromFileName(&info, fileName)
				info.staid = strings.ToUpper(info.staid)
				fileList = append(fileList, info)
			}
		} else if ext == ".CRX" || strings.HasSuffix(fileNameUpper, "D") || strings.HasSuffix(fileNameUpper, "O") {
			info := fileInfo{fullName: dir + gobase.PathSeparator + fileName}
			err := this.processRnxObsFile(&info, info.fullName)
			if err != nil {
				golog.Errorf("%s err %s", fileName, err.Error())
			} else {
				extractFromFileName(&info, fileName)
				info.staid = strings.ToUpper(info.staid)
				fileList = append(fileList, info)
			}
		}
		return true
	}, func(dirname string, err error) bool {
		golog.Errorf("%s, err:%s", dirname, err)
		return true
	})
	if len(outputFile) == 0 {
		outputFile = dir
	}

	err := this.writeStaFile(outputFile, fileList)
	if err != nil {
		gobase.Warnf("sta, %s", err.Error())
	}

	gobase.Infof("%d, consume:%d(ms)", len(fileList), time.Since(t0).Milliseconds())
	// fmt.Printf("%s", sb.String())
}
